package com.share.tx.util;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.expression.AccessException;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.stereotype.Service;

/*
 * retry执行sql过程：count是proxy类的属性，proxy类只被spring加载一次，存放在spring容器中。所以不管proxy类的方法执行成功还是失败，都要把count重置。
 *                但是当两个线程同时调用proxy类时，因为proxy类及其属性是线程共有的，所以count属性会累记增加。
 *                所以用线程绑定变量ThreadLocal，让count属性成为线程私有的。
 *                单线程中会有多个proxy类，但是这多个proxy类的方法肯定是按顺序执行的。所以每个proxy类的方法不管执行成功还是失败，都要把count重置。
 *                
 *         经测试：此方式会在切service包时产生死循环，因为mapper抛出的异常会被dbservice catch住
 * 
 */
@Service
public class MyAspect_aop implements MethodInterceptor{

	//	@Override
	//	public Object invoke(MethodInvocation mi) throws Throwable {
	//		System.out.println("前方法");
	//		Object obj = mi.proceed();
	//		System.out.println("后方法");
	//		return obj;
	//	}

	@Resource
	private SqlSessionFactory sqlSessionFactory;


	public static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){
		//通过匿名内部类覆盖ThreadLocal的InitialValue方法，指定初值
		protected Integer initialValue() {
			return 1;
		};
	};

	//	int count = 1;

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		String flag = getMethodString(invocation);
		long start = System.currentTimeMillis();
		try {
			//int a = 1/0;
			Object result = invocation.proceed();//此方式会在切service包时产生死循环,因为mapper抛出的异常会被dbservice catch住
			StringBuilder sb = new StringBuilder();
			long costms = System.currentTimeMillis() - start;
			//执行成功，重置
			count.set(1);
			//            count = 1;
			sb.append("Executed " + flag + " [ timeCost -> " + costms
					+ " ms , result(size) -> ");
			if (result instanceof List) {
				sb.append(((List<?>) result).size());
			} else if (result instanceof Map) {
				sb.append(((Map<?, ?>) result).size());
			} else {
				sb.append(result);
			}
			sb.append(" ]");         
			//耗时超过1000ms，则以ERROR日志打出。
			logHandler(invocation, costms, sb);
			return result;

		} catch (BadSqlGrammarException e) {
			System.out.println("count :  " + count.get().toString());
			if (count.get() <= 3) {
				Thread.sleep(2000);
				System.out.println(flag + " 重试次数：" + count.get());
				count.set(count.get()+1);
				return invoke(invocation);
			}else{
				System.out.println(flag + "一共重试次3次: " + "失败");
				//执行失败，重置
				count.set(1);
				throw e;
			}
		} catch (DataAccessException e) {//ArithmeticException  DataAccessException

			StringBuilder sb = new StringBuilder();
			sb.append("Executed " + flag + " [ timeCost -> " + (System.currentTimeMillis() - start) + " ms ] ");
			sb.append(e.getClass().getName() + ",");
			sb.append(e.toString());
			System.out.println(sb.toString());
			throw e;


			//        	if (count <= 3) {
			//        		Thread.sleep(3000);
			//        		System.out.println(flag + " 重试次数：" + count++);
			//        		return invoke(invocation);
			//			}else{
			//				System.out.println(flag + "一共重试次3次: " + "失败");
			//				//执行失败，重置
			//				count = 1;
			//				throw e;
			//			}
		} catch (Exception e) {
			throw e;
		}
	}

	/**
	 * logHandle:日志处理，耗时超过1000ms，则以ERROR日志打出。并打出sql文本
	 * @param invocation
	 * @param costms
	 * @param sb
	 */
	private void logHandler(MethodInvocation invocation, long costms, StringBuilder sb){
		if(costms > 1000){
			String name = invocation.getMethod().getDeclaringClass().getName()+"."+ invocation.getMethod().getName();
			if (isMapperSql(name)) {
				sb.append(getMyBatisSql(name, null, sqlSessionFactory));
				System.out.println("超时： " + sb.toString());
			}	
		}else{
			System.out.println("未超时：" + sb.toString());
		}
	}

	/**
	 * GetMethodString: 获取MethodInvocation的输出文本
	 * @param invocation
	 * @return 输出文本
	 */
	private static String getMethodString(MethodInvocation invocation) {
		Method method = invocation.getMethod();
		StringBuffer sb = new StringBuffer();
		sb.append(method.getDeclaringClass().getSimpleName());
		sb.append("@[");
		sb.append(method.getReturnType().getSimpleName()).append(" ");
		sb.append(method.getName());
		sb.append("(");
		@SuppressWarnings("rawtypes")
		Class[] params = method.getParameterTypes();
		for (int j = 0; j < params.length; j++) {
			// TODO 参数是泛型是，无法区分两个方法，后续补充
			sb.append(params[j].getSimpleName());
			if (j < (params.length - 1)) {
				sb.append(",");
			}
		}
		sb.append(")]");
		return sb.toString();
	}

	/**
	 * getMyBatisSql: 获取sql语句的输出文本
	 * @param id parameter sqlSessionFactory
	 * @return sql语句
	 */
	private String getMyBatisSql(String id, Object[] parameter,SqlSessionFactory sqlSessionFactory) { 
		String sql = "";
		try {
			MappedStatement ms = sqlSessionFactory.getConfiguration().getMappedStatement(id); 
			BoundSql boundSql = ms.getBoundSql(parameter);  
			sql = boundSql.getSql(); 
		} catch (Exception e) {
			System.out.println(e);
		}
		return sql;
	}

	/**
	 * isMapperSql:是否为mapper
	 * @param id
	 * @return boolean
	 */
	private boolean isMapperSql(String id){
		if(id!=null && id.contains("Mapper")){
			return true;
		}else{
			return false;
		}
	}

}
